Skip to content

feat(plugins): add sandboxed editor panels and actions - #3188

Merged
ascorbic merged 10 commits into
codex/block-kit-navigation-localefrom
codex/plugin-editor-panels-actions
Sep 20, 2026
Merged

ascorbic merged 10 commits into
codex/block-kit-navigation-localefrom
codex/plugin-editor-panels-actions

Conversation

@ascorbic

@ascorbic ascorbic commented Sep 17, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

Adds saved-entry panels and actions for sandboxed plugins, stacked on the safe Block Kit navigation and locale work in #3174.

  • Adds bounded, collection-filtered admin.editorPanels and admin.editorActions declarations across the CLI schema, wire manifest, npm descriptor, configured and runtime-installed plugin paths, marketplace validation, and admin manifest.
  • Adds a dedicated saved-entry POST boundary that reloads the canonical entry, checks ownership and the declared private-route permission, and attaches only collection, ID, locale, version, extension ID, and admin locale/direction to routeCtx.ui. Unsaved field values never cross the sandbox boundary.
  • Loads each panel only when expanded, validates its Block Kit response, reloads it after the saved version changes, and isolates transport and rendering failures per panel.
  • Renders toolbar and overflow actions with Kumo, requires manifest confirmation for danger actions, disables actions while edits are unsaved, and accepts only bounded toast, refresh, or structured-navigation responses.
  • Extends createPluginRuntimeTestHost() with production-boundary panel and action helpers and updates public docs plus canonical/generated plugin-authoring skills.

Related feature discussion: #3004. The discussion's unsaved-draft snapshot and editor-patch proposal remains out of scope; this first release exposes saved identity only and operates on the persisted entry.

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — a workflow extracts catalogs on merge to main.
  • I have added and reviewed the user-facing changeset (if this PR changes a published package)
  • New features link to an approved Discussion: Add a Block Kit placement for sandboxed content-editor actions #3004
  • I have included screenshots below if this PR changes the UI

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: GPT-5 Codex

Screenshots / test output

The first screenshot shows the saved-entry action and collapsed plugin panel in the Arabic right-to-left editor. The second shows the same panel expanded after its lazy request, including host-attested saved identity and the explicit notice that unsaved editor changes are not sent to the plugin.

Validation completed at 1797935204c8cc18415cd3d6b6063a55674912df:

  • pnpm build
  • pnpm typecheck
  • pnpm lint
  • pnpm lint:quick
  • pnpm lint:json | jq '.diagnostics | length' (0)
  • pnpm format
  • repository package tests: every non-core package passed in the combined run; the full core rerun passed 6,911 tests with 10 skipped
  • 2,368 admin browser tests
  • 488 plugin CLI tests
  • 143 Block Kit tests
  • 72 shared plugin-manifest tests
  • 46 marketplace tests
  • 16 runtime-backed Worker Loader host tests
  • focused real Node-fixture Playwright journey in Arabic RTL: 2 tests
  • pnpm --dir docs build (85 pages)
  • pnpm changeset status --since=origin/main

The mandatory independent adversarial review found stale loaded panels and two marketplace/core schema parity failures. Those were fixed with regression coverage and re-reviewed at the current head; the final review reports no bugs.

Arabic RTL content editor showing the saved-entry plugin action and collapsed plugin panel

Arabic RTL content editor showing the expanded plugin panel with host-attested saved identity and unsaved-content notice

@ascorbic
ascorbic added this pull request to stack #3189 September 17, 2026 21:22
@changeset-bot

changeset-bot Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: c6132ad

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 15 packages
Name Type
@emdash-cms/admin Minor
@emdash-cms/blocks Minor
@emdash-cms/plugin-cli Minor
@emdash-cms/plugin-test Minor
@emdash-cms/plugin-types Minor
emdash Minor
@emdash-cms/plugin-embeds Patch
@emdash-cms/registry-verification Patch
@emdash-cms/cloudflare Minor
@emdash-cms/sandbox-workerd Patch
@emdash-cms/auth Minor
create-emdash Minor
@emdash-cms/gutenberg-to-portable-text Minor
@emdash-cms/x402 Minor
@emdash-cms/auth-atproto Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added the review/needs-review No maintainer or bot review yet label Sep 17, 2026
@ascorbic ascorbic changed the title codex/plugin editor panels actions feat(plugins): add sandboxed editor panels and actions Sep 17, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs 3e63a60 Sep 20 2026, 02:56 AM

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 3,585 lines across 70 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@pkg-pr-new

pkg-pr-new Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/admin@3188

@emdash-cms/auth

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth@3188

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/auth-atproto@3188

@emdash-cms/blocks

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/blocks@3188

@emdash-cms/cloudflare

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/cloudflare@3188

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/contentful-to-portable-text@3188

emdash

npm i https://pkg.pr.new/emdash-cms/emdash@3188

create-emdash

npm i https://pkg.pr.new/emdash-cms/emdash/create-emdash@3188

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/gutenberg-to-portable-text@3188

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-cli@3188

@emdash-cms/plugin-test

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-test@3188

@emdash-cms/plugin-types

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-types@3188

@emdash-cms/registry-client

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-client@3188

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-lexicons@3188

@emdash-cms/registry-loader

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-loader@3188

@emdash-cms/registry-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-moderation@3188

@emdash-cms/registry-verification

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/registry-verification@3188

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/sandbox-workerd@3188

@emdash-cms/x402

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/x402@3188

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-ai-moderation@3188

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-atproto@3188

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-audit-log@3188

@emdash-cms/plugin-color

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-color@3188

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-embeds@3188

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-field-kit@3188

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-forms@3188

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/emdash-cms/emdash/@emdash-cms/plugin-webhook-notifier@3188

commit: c6132ad

github-actions Bot pushed a commit that referenced this pull request Sep 17, 2026
github-actions Bot pushed a commit that referenced this pull request Sep 17, 2026
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

🚀 Deploying Preview to Cloudflare 🚀

Preview URL: https://codex-plugin-editor-panels-actions.try.emdashcms.com, https://codex-plugin-editor-panels-actions-emdash-playground.emdash-cms.workers.dev (commit c6132ad)

This URL reflects your latest Preview deployment

Preview Deployments by commit

Status Deployment URL Commit Updated (UTC) See this deployment's details
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://a8c5590b.try.emdashcms.com, https://a8c5590b-emdash-playground.emdash-cms.workers.dev c6132ad 2026-09-20T04:08:07.526Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://cb22a6c6.try.emdashcms.com, https://cb22a6c6-emdash-playground.emdash-cms.workers.dev 3e63a60 2026-09-20T02:56:21.154Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://b0fff7fd.try.emdashcms.com, https://b0fff7fd-emdash-playground.emdash-cms.workers.dev 7643ab0 2026-09-19T10:26:34.312Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://97075d3c.try.emdashcms.com, https://97075d3c-emdash-playground.emdash-cms.workers.dev ab1bf67 2026-09-19T08:04:11.908Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://11c2a423.try.emdashcms.com, https://11c2a423-emdash-playground.emdash-cms.workers.dev dae0c31 2026-09-18T11:37:48.114Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://04171b88.try.emdashcms.com, https://04171b88-emdash-playground.emdash-cms.workers.dev 504c43c 2026-09-18T11:07:19.749Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://bc9b8d52.try.emdashcms.com, https://bc9b8d52-emdash-playground.emdash-cms.workers.dev a066987 2026-09-18T10:25:36.629Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://a9d5b2ab.try.emdashcms.com, https://a9d5b2ab-emdash-playground.emdash-cms.workers.dev 18148fc 2026-09-18T09:50:47.794Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://5c569dd7.try.emdashcms.com, https://5c569dd7-emdash-playground.emdash-cms.workers.dev c26d112 2026-09-18T06:25:21.918Z Visit the dashboard ↗
  • Build: Success ✅
  • Deployment: Success ✅

View logs ↗
https://de0e5382.try.emdashcms.com, https://de0e5382-emdash-playground.emdash-cms.workers.dev 1797935 2026-09-17T21:26:08.302Z Visit the dashboard ↗

@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@github-actions github-actions Bot mentioned this pull request Sep 18, 2026
11 of 19 tasks
github-actions Bot pushed a commit that referenced this pull request Sep 18, 2026
@github-actions github-actions Bot mentioned this pull request Sep 18, 2026
11 of 19 tasks
@ascorbic

Copy link
Copy Markdown
Member Author

/accept-baselines

@github-actions

Copy link
Copy Markdown
Contributor

✅ Baselines accepted by @ascorbic and committed to this PR.

@ascorbic

Copy link
Copy Markdown
Member Author

/review

@ask-bonk ask-bonk Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Focused adversarial review of the new saved-entry panel/action surface. Found one UX regression that ships user-visible pain on every autosave, plus a smaller state-management issue and a toast type mismatch.

Comment thread packages/admin/src/components/ContentSettingsPanel.tsx Outdated
Comment thread packages/admin/src/components/SandboxedContentEditorActions.tsx
Comment thread packages/admin/src/components/SandboxedContentEditorPanel.tsx
Comment thread packages/admin/src/components/SandboxedContentEditorActions.tsx
@ask-bonk

ask-bonk Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Review posted successfully at #3188 (review).

Summary of findings:

  • HIGH — Plugin panel state destroyed on every autosave (ContentSettingsPanel.tsx:1181 + SandboxedContentEditorPanel.tsx): Folding _rev/updatedAt into the boundary key and the panel's identity means every 2-second autosave collapses and unloads any expanded plugin panel while the user is typing.
  • LOW — Pending state leak on identity oscillation (SandboxedContentEditorActions.tsx:138): The finally block skips cleanup when identity has changed, relying on the identity-change effect — reachable in a narrow but real scenario where a request's finally runs after identity flips and the effect has already fired for that transition.
  • LOW — Kumo Toast doesn't support the "info" type (both new components): BlockResponse.toast.type advertises "info" but the admin nowhere else uses it and Kumo's Toast only handles "success"/"error".
  • LOW — Danger actions lack visual distinction in the overflow menu (SandboxedContentEditorActions.tsx:227): The toolbar variant uses destructive, but the overflow DropdownMenu.Item gets no styling for style: "danger".

github run

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond review/needs-rereview New commits since the last review and removed review/needs-review No maintainer or bot review yet review/awaiting-author Reviewed; waiting on the author to respond labels Sep 18, 2026
@ascorbic

Copy link
Copy Markdown
Member Author

/review

@ask-bonk

ask-bonk Bot commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

Wait — this reset effect fires on mount too (since [panelIdentity] starts as the initial value). So on initial mount, setOpen(false) etc. are all called. They're all defaults, so no visible change. Good.

But generation.current++ on cleanup and on the next mount can cause weird counters. Not a real bug.

OK, let me write and post my review. My verdict: The existing review by ask-bonk was strong. The two fixes look correct. The remaining nits (toast type, pending state) are either author-correct or pre-existing patterns. I found one minor new issue (rapid toggle race).

Given that this is a targeted follow-up review after the previous one where the primary fixes are in, and my only new finding is a very low-severity edge case, I'll post a small focused review noting the rapid-toggle race and confirming the two fixes look correct.

Actually — let me check one more time if the rapid open/close/open bug is real. React 18 batches setState in event handlers. Let me think about handleOpenChange from Kumo's Collapsible.

Actually, Collapsible.Root's onOpenChange triggers on user click. If the user clicks close then click open in quick succession, these are two separate synthetic events, each triggering its own render cycle in React 18. Each event's handler sees the state as of its render. So:

  1. Panel state: {open: true, loaded: false, loading: true, ...}. User clicks close.
  2. handleOpenChange(false): setOpen(false); setLoading(false); setLoaded(false) are queued.
  3. React re-renders. Now state is {open: false, loaded: false, loading: false}. handleOpenChange closure captures fresh state.
  4. User clicks open.
  5. handleOpenChange(true): setOpen(true). Then nextOpen && !loaded && !loading = true && true && true = true → sendInteraction. Good.

So the race requires the two clicks to happen within one React batch. That would need synchronous programmatic clicks. Highly unlikely in practice. Not a real user-triggered bug.

I'm going to skip that finding. My conclusion: the previous review's fixes are correct, and I have no new bugs to report at severity worth mentioning.

Given the extensive validation, resolved review, and confirmed fixes, this looks good.

LGTM!

github run

@ascorbic
ascorbic force-pushed the codex/plugin-editor-panels-actions branch from ab1bf67 to 7643ab0 Compare September 19, 2026 10:22
@ascorbic
ascorbic force-pushed the codex/plugin-editor-panels-actions branch from 7643ab0 to 3e63a60 Compare September 20, 2026 02:52
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Sep 20, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR adds saved-entry editor panels and actions for plugins in a sound way: it reloads and ownership-authorizes the entry before crossing the sandbox boundary, exposes only canonical identity/locale/version, and wires the UI lazily. The manifest, runtime, admin, and CLI changes are consistent, and the tests exercise auth, validation, and the plugin-test host.

There are two blockers in packages/core/src/plugins/http-route-dispatch.ts:

  1. The dispatch helper loads the saved entry before it has verified the caller. An unauthenticated or forged request can therefore trigger a content-table query and observe success/failure timing. It should reject unauthenticated callers (and ideally validate route permission/CSRF) before the database read.
  2. It rebuilds the request body while copying the original Content-Length header. That header describes the client’s original payload, so action requests (where the body is replaced with { "type": "editor_action" }) can be sent to the plugin route with a stale length, risking truncated/padded body parsing.

A smaller suggestion: the marketplace manifest schema hand-maintains a copy of the core permission list; using the canonical Permissions object would prevent drift.

Once the auth-order and content-length issues are addressed, this should be good to land.

Comment thread packages/core/src/plugins/http-route-dispatch.ts
Comment thread packages/core/src/plugins/http-route-dispatch.ts
Comment thread packages/marketplace/src/routes/author.ts Outdated
@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Sep 20, 2026
@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-rereview New commits since the last review labels Sep 20, 2026
@github-actions github-actions Bot added review/needs-rereview New commits since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Sep 20, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Sep 20, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of PR #3188: the two blockers from the previous review are resolved, and the marketplace/core schema parity gap is closed.

What changed since the last review:

  • packages/core/src/plugins/http-route-dispatch.ts now authenticates the caller (route permission, admin token scope, and CSRF header) before it reloads the saved entry. Unauthenticated or forged requests no longer reach handleContentGet.
  • The reconstructed plugin request now explicitly deletes content-length and content-encoding, so the new JSON body cannot be parsed with stale chunked/length information.
  • packages/marketplace/src/routes/author.ts now validates route permissions against the canonical Permissions object from @emdash-cms/auth and includes editorPanels/editorActions schemas and duplicate/private-route checks matching core and @emdash-cms/plugin-types.
  • Regression tests cover the auth-order, content-length, and schema-parity cases.

I re-checked the whole diff for the other cross-cutting concerns in AGENTS.md:

  • Authorization follows requirePerm/requireOwnerPerm; permission strings are validated against Permissions; inherited object keys are rejected with Object.hasOwn.
  • CSRF is enforced via X-EmDash-Request before any content query.
  • The route file follows Astro route conventions (prerender = false, thin wrapper, apiError).
  • User-facing strings in the new React components are Lingui-wrapped, and no new physical-direction Tailwind classes were introduced.
  • The changeset is user-facing and proportional to the feature.
  • No SQL, index, or logged-out hot-path changes.

The implementation fits EmDash’s architecture: saved identity is reloaded and ownership-checked on the host side, only canonical collection/id/locale/version crosses to the plugin, unsaved field values never leave the browser, and responses are validated through the existing Block Kit/action validators. The tests exercise both the unit boundary and end-to-end browser behavior in RTL.

No further findings — this looks ready to land.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Sep 20, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview New commits since the last review labels Sep 20, 2026
@ascorbic
ascorbic merged commit 4fef109 into main Sep 20, 2026
89 of 91 checks passed
@ascorbic
ascorbic deleted the codex/plugin-editor-panels-actions branch September 20, 2026 04:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant